#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_maxlab_testffmpeg_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
{
#include <libavcodec/avcodec.h>

JNIEXPORT jstring JNICALL
Java_com_maxlab_testffmpeg_MainActivity_ffmpegVersion(JNIEnv *env, jobject  /* this */) {

    std::string info = av_version_info();
    return env->NewStringUTF(info.c_str());
}



#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/log.h"
#include "android_log.h"


JNIEXPORT jboolean JNICALL
Java_com_maxlab_testffmpeg_MainActivity_initH265(JNIEnv *env, jobject  /* this */) {
    av_register_all();
    avformat_network_init();

    //设置ffmpeg解码器类型
    AVCodecID id = AV_CODEC_ID_H264;
    //id = AV_CODEC_ID_HEVC;
    const AVCodec *codec;
    codec = avcodec_find_decoder(id);
    if (!codec) {
        fprintf(stderr, "Codec not found AV_CODEC_ID_H264\n");
        return false;
    }

//    AVCodecContext *c;
//    c = avcodec_alloc_context3(codec);
//    if (!c) {
//        fprintf(stderr, "Could not allocate audio codec context\n");
//        return false;
//    }
//
//    /* open it */
//    if (avcodec_open2(c, codec, NULL) < 0) {
//        fprintf(stderr, "Could not open codec\n");
//        return false;
//    }
    /*packet = av_packet_alloc();
    if (NULL == packet)
    {
        return false;
    }*/
    /*frame = av_frame_alloc();
    if (NULL == frame)
    {
        return false;
    }*/

    return true;
}

//test different codec
#define TEST_H264  1
#define TEST_HEVC  0

JNIEXPORT jboolean JNICALL
Java_com_maxlab_testffmpeg_MainActivity_decoderPure(JNIEnv *env, jobject  /* this */) {
    AVCodec *pCodec;
    AVCodecContext *pCodecCtx= NULL;
    AVCodecParserContext *pCodecParserCtx=NULL;

    FILE *fp_in;
    FILE *fp_out;
    AVFrame	*pFrame;

    const int in_buffer_size=4096;
    unsigned char in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};
    unsigned char *cur_ptr;
    int cur_size;
    AVPacket packet;
    int ret, got_picture;

#if TEST_HEVC
    enum AVCodecID codec_id=AV_CODEC_ID_HEVC;
	char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264
    AVCodecID codec_id=AV_CODEC_ID_H264;
    //char filepath_in[]="bigbuckbunny_480x272.h264";
    char filepath_in[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272.h264";

#else
    AVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;
	char filepath_in[]="bigbuckbunny_480x272.m2v";
#endif

    //char filepath_out[]="bigbuckbunny_480x272.yuv";
    char filepath_out[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272.yuv";
    int first_time=1;


    //av_log_set_level(AV_LOG_DEBUG);

    avcodec_register_all();

    pCodec = avcodec_find_decoder(codec_id);
    if (!pCodec) {
        printf("Codec not found\n");
        return -1;
    }
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx){
        printf("Could not allocate video codec context\n");
        return -1;
    }

    pCodecParserCtx=av_parser_init(codec_id);
    if (!pCodecParserCtx){
        printf("Could not allocate video parser context\n");
        return -1;
    }

    //if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
    //    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED;

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec\n");
        return -1;
    }
    //Input File
    fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return -1;
    }
    //Output File
    fp_out = fopen(filepath_out, "wb");
    if (!fp_out) {
        printf("Could not open output YUV file\n");
        return -1;
    }

    pFrame = av_frame_alloc();
    av_init_packet(&packet);

    while (1) {

        cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
        if (cur_size == 0)
            break;
        cur_ptr=in_buffer;

        while (cur_size>0){

            int len = av_parser_parse2(
                    pCodecParserCtx, pCodecCtx,
                    &packet.data, &packet.size,
                    cur_ptr , cur_size ,
                    AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

            LOGD("len=%d",len);

            cur_ptr += len;
            cur_size -= len;

            if(packet.size==0)
                continue;

            LOGD("av_parser_parse2 len=%d",len);
            LOGD("[Packet]Size:%6d\t",packet.size);
            //Some Info from AVCodecParserContext
            printf("[Packet]Size:%6d\t",packet.size);
            switch(pCodecParserCtx->pict_type){
                case AV_PICTURE_TYPE_I: printf("Type:I\t");break;
                case AV_PICTURE_TYPE_P: printf("Type:P\t");break;
                case AV_PICTURE_TYPE_B: printf("Type:B\t");break;
                default: printf("Type:Other\t");break;
            }
            printf("Number:%4d\n",pCodecParserCtx->output_picture_number);

            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
            if (ret < 0) {
                printf("Decode Error.\n");
                return ret;
            }
            if (got_picture) {
                if(first_time){
                    printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
                    printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
                    first_time=0;
                }
                //Y, U, V
                for(int i=0;i<pFrame->height;i++){
                    fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
                }
                for(int i=0;i<pFrame->height/2;i++){
                    fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
                }
                for(int i=0;i<pFrame->height/2;i++){
                    fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
                }

                printf("Succeed to decode 1 frame!\n");
            }
        }

    }

    //Flush Decoder
    packet.data = NULL;
    packet.size = 0;
    while(1){
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
        if (ret < 0) {
            printf("Decode Error.\n");
            return ret;
        }
        if (!got_picture){
            break;
        }else {
            //Y, U, V
            for(int i=0;i<pFrame->height;i++){
                fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
            }
            for(int i=0;i<pFrame->height/2;i++){
                fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
            }
            for(int i=0;i<pFrame->height/2;i++){
                fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
            }

            printf("Flush Decoder: Succeed to decode 1 frame!\n");
        }
    }

    fclose(fp_in);
    fclose(fp_out);


    av_parser_close(pCodecParserCtx);

    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    av_free(pCodecCtx);



    return true;
}

#include "Decoder.h"

class CDecoderGotPictureListener_save : public IDecoderGotPictureListener {
    FILE *fp_out;
public:
    CDecoderGotPictureListener_save() {
        //char filepath_out[]="bigbuckbunny_480x272.yuv";
        char filepath_out[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272_from_class.yuv";
        //Output File
        fp_out = fopen(filepath_out, "wb");
        if (!fp_out) {
            printf("Could not open output YUV file\n");
        }
    };

    ~CDecoderGotPictureListener_save() {
        fclose(fp_out);
    }

    void onGotPicture(unsigned char *out_buffer, int out_buffer_size) {
        fwrite(out_buffer,1,out_buffer_size,fp_out);
    }

};

JNIEXPORT jboolean JNICALL
Java_com_maxlab_testffmpeg_MainActivity_decoderClass(JNIEnv *env, jobject  /* this */) {


    FILE *fp_in;
    //FILE *fp_out;
    const int in_buffer_size=4096;
    unsigned char in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};
    unsigned char *cur_ptr;
    int cur_size;
    CDecoder decoder;
    IDecoderGotPictureListener * decoderGotPictureListener = new CDecoderGotPictureListener_save();

    //char filepath_in[]="bigbuckbunny_480x272.h264";
    char filepath_in[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272.h264";
    //char filepath_out[]="bigbuckbunny_480x272.yuv";
    //char filepath_out[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272.yuv";

    if(!decoder.init(DecoderType_H264, decoderGotPictureListener)) {
        return false;
    }

    //Input File
    fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return false;
    }
    //Output File
//    fp_out = fopen(filepath_out, "wb");
//    if (!fp_out) {
//        printf("Could not open output YUV file\n");
//        return false;
//    }

    while (1) {

        cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
        if (cur_size == 0)
            break;
        cur_ptr=in_buffer;



        while (cur_size>0){
            int len = 0;
            bool ret = decoder.decode(cur_ptr,cur_size, len);
            LOGD("decoder.decode len=%d",len);

            cur_ptr += len;
            cur_size -= len;

            if(ret == false) {
                continue;
            }

        }

    }

    fclose(fp_in);
    //fclose(fp_out);

    decoder.destroy();

    return true;
}

JNIEXPORT jboolean JNICALL
Java_com_maxlab_testffmpeg_MainActivity_decoderClass2(JNIEnv *env, jobject  /* this */) {

    FILE *fp_in;
    FILE *fp_out;
    const int in_buffer_size=4096;
    unsigned char in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};
    unsigned char *cur_ptr;
    int cur_size;
    CDecoder decoder;
    IDecoderGotPictureListener * decoderGotPictureListener = new CDecoderGotPictureListener_save();

    //char filepath_in[]="bigbuckbunny_480x272.h264";
    char filepath_in[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272.h264";
    //char filepath_out[]="bigbuckbunny_480x272.yuv";
    char filepath_out[]="/storage/emulated/0/Android_Test/bigbuckbunny_480x272_from_class2.yuv";

    if(!decoder.init(DecoderType_H264)) {
        return false;
    }

    //Input File
    fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return false;
    }
    //Output File
    fp_out = fopen(filepath_out, "wb");
    if (!fp_out) {
        printf("Could not open output YUV file\n");
        return false;
    }

    while (1) {

        cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
        if (cur_size == 0)
        break;
        cur_ptr=in_buffer;


        while (cur_size>0){

            bool ret = decoder.decode(cur_ptr,cur_size);
            LOGD("decoder.decode len=%d", decoder.getDecodeResult()->m_nLen);

            int len = decoder.getDecodeResult()->m_nLen;
            int got_picture = decoder.getDecodeResult()->m_nGotPicture;

            cur_ptr += len;
            cur_size -= len;

            if(ret == false) {
                continue;
            }

            if (!got_picture){
                break;
            }else {
                fwrite(decoder.getDecodeResult()->m_ptOutBuffer,1, decoder.getDecodeResult()->m_nOutBufferSize,fp_out);
            }
        }
    }

    fclose(fp_in);
    fclose(fp_out);

    decoder.destroy();

    return true;
}

JNIEXPORT jlong JNICALL
Java_com_maxlab_testffmpeg_MainActivity_createNativeDecoder(
        JNIEnv *env, jclass obj,  jint type) {
    CDecoder *decoder = new CDecoder();
    if(!decoder->init((DecoderType)type)) {
        return 0;
    }
    return reinterpret_cast<jlong>(decoder);
}

JNIEXPORT jboolean JNICALL
Java_com_maxlab_testffmpeg_MainActivity_decodeNativeDecoder(
        JNIEnv *env, jclass obj, jlong nativeObj, jbyteArray byte_scr, jobject resultObj) {
    CDecoder *decoder = reinterpret_cast<CDecoder *>(nativeObj);

    if (decoder == NULL) {
        return false;
    }

    jboolean inputCopy = JNI_FALSE;
    //取出byte[]的指针
    uint8_t* byte_scr_ptr = reinterpret_cast<uint8_t*>(env->GetByteArrayElements(byte_scr, &inputCopy));
    //获取数组长度
    jint length = env->GetArrayLength(byte_scr);

    bool result = decoder->decode(byte_scr_ptr, length);

    CDecodeResult* decodeResult = decoder->getDecodeResult();
    int cur_size = length;
    uint8_t* cur_ptr = byte_scr_ptr;


    while (cur_size>0){

        bool ret = decoder->decode(cur_ptr,cur_size);
        LOGD("decoder.decode len=%d", decoder->getDecodeResult()->m_nLen);

        int len = decoder->getDecodeResult()->m_nLen;
        int got_picture = decoder->getDecodeResult()->m_nGotPicture;

        cur_ptr += len;
        cur_size -= len;

        if(ret == false) {
            continue;
        }

        if (!got_picture){
            break;
        }
    }


    return result;
}

#define SAFE_DELETE(x) if (nullptr != x) { delete x; x = nullptr; } // NOLINT
JNIEXPORT void JNICALL
Java_com_maxlab_testffmpeg_MainActivity_destroyNativeDecoder(
        JNIEnv *env, jclass obj, jlong nativeObj) {
    CDecoder *decoder = reinterpret_cast<CDecoder *>(nativeObj);

    if (decoder) {
        SAFE_DELETE(decoder);
    }
}

}
